﻿using Enough.Lifecycle.Common;
using Enough.Lifecycle.Helper;
using Enough.Storage;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.UI.Core;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

namespace Enough.Lifecycle.ViewModel
{

    /// <summary>
    /// A base class for view models that integrates the following life cycle methods:
    /// - OnAppActivated: when the app is reactivated, e.g. after taking a snapshot
    /// - OnNavigatedTo: when the page to which this VM belongs is being visited
    /// - OnNavigatedFrom: when the page to which this VM belongs is left
    /// - OnHardwareButtonsBackPressed: on Windows Phone when the hardware back key is being pressed
    /// - OnInitializeAsync: when the state of the view model should be restored
    /// </summary>
    public class BaseViewModel : ObservableObject
    {

        /// <summary>
        /// Determines if this view model is currently using the design mode, i.e. within blend or in the Visual Studio designer.
        /// </summary>
        public bool IsInDesignMode
        {
            get 
            {
                return Windows.ApplicationModel.DesignMode.DesignModeEnabled
                    || isInDesignModeRT();
            }
        }

        private static bool isInDesignModeRT()
        {
            try
            {
                var dm = Type.GetType("Windows.ApplicationModel.DesignMode, Windows, ContentType=WindowsRuntime");

                var dme = dm.GetTypeInfo().GetDeclaredProperty("DesignModeEnabled");
                return (bool)dme.GetValue(null, null);
            }
            catch
            {
                return false;
            }
        }



        private bool _isBusy;
        /// <summary>
        /// Indicator for this View Model being busy at the moment.
        /// Typically you will bind the Visibility state of a ProgressBar or ProgressRing to this state.
        /// </summary>
        /// <seealso cref="IncreaseBusyCounter"/>
        /// <seealso cref="DecreaseBusyCounter"/>
        public bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (value != _isBusy)
                {
                    _isBusy = value;
                    RaisePropertyChanged();
                }
            }
        }

        private int _busyCounter;
        /// <summary>
        /// Used to notify about having started with a long running process.
        /// </summary>
        public void IncreaseBusyCounter()
        {
            lock (this)
            {
                _busyCounter++;
                IsBusy = true;
            }
        }

        /// <summary>
        /// Used to notify having finished with a long running process.
        /// </summary>
        public void DecreaseBusyCounter()
        {
            lock (this)
            {
                int counter = _busyCounter - 1;
                if (counter < 0)
                {
                    counter = 0;
                }
                _busyCounter = counter;
                if (counter == 0)
                {
                    IsBusy = false;
                }
            }
        }

        /// <summary>
        /// Used by the life cycle management to determine whether this view model requires a potentially long running initialization.
        /// </summary>
        public bool IsInitialized { get; set; }


        internal async Task CallInitializeAsync()
        {
            await OnInitializeAsync();
            // TODO extract known types
            string fileName = getFileName();
            Dictionary<string, object> restorableAttributeValues = await StorageHelper.TryLoadObjectAsync<Dictionary<string, object>>(fileName);
            if (restorableAttributeValues != null)
            {
                var properties = this.GetType().GetRuntimeProperties();
                foreach (var keyValuePair in restorableAttributeValues)
                {
                    PropertyInfo propertyInfo = properties.FirstOrDefault(p => p.Name == keyValuePair.Key);
                    if (propertyInfo != null)
                    {
                        propertyInfo.SetValue(this, keyValuePair.Value);
                    }
                }
            }
            IsInitialized = true;
        }

        /// <summary>
        /// Allows subclasses to restore dependencies when this view model class is either loaded for the first time
        /// or when this view model is restored.
        /// <para>
        /// This method is called before the individual properties are restored.
        /// </para>
        /// </summary>
        /// <returns>this call is awaitable</returns>
        public async virtual Task OnInitializeAsync()
        {

        }

        internal async Task CallNavigatedToAsync(NavigationEventArgs e)
        {
            if (!IsInitialized)
            {
                await CallInitializeAsync();
            }
            OnNavigatedTo(e);
        }

        internal async Task CallNavigatedFromAsync(NavigationEventArgs e)
        {
            OnNavigatedFrom(e);
            // save the restorable attributes:
            // TODO extract known types, but: this doesn't help when Frame.GetNavigationStack supports only primitives
            Dictionary<string, object> restorableAttributeValues = new Dictionary<string, object>();
            var properties = this.GetType().GetRuntimeProperties();
            foreach (PropertyInfo propertyInfo in properties)
            {
                object value = propertyInfo.GetValue(this);
                if (value != null)
                {
                    if (propertyInfo.GetCustomAttribute<Restorable>() != null)
                    {
                        restorableAttributeValues.Add(propertyInfo.Name, value);
                    }
                }
            }
            if (restorableAttributeValues.Count > 0)
            {
                string fileName = getFileName();
                await StorageHelper.SaveObjectAsync(restorableAttributeValues, fileName);
            }
        }

        private string getFileName()
        {
            return "lifecycle_" + GetType().Name;
        }

        /// <summary>
        /// Translates the given resource
        /// </summary>
        /// <param name="resourceName">the name of the resource within Resources.rews</param>
        /// <param name="parameters">optional parameters for filling in {0}, {1}, etc</param>
        /// <returns>the translated resource, null if it is not known</returns>
        public string Localize(string resourceName, params object[] parameters)
        {
            return LocalizationHelper.Localize(resourceName, parameters);
        }


        public void NavigateTo<T>(object parameter = null) where T : Page
        {
            //if (parameter != null)
            //{
            //    Type parameterType = parameter.GetType();
            //    if (!SuspensionManager.KnownTypes.Contains(parameterType))
            //    {
            //        SuspensionManager.KnownTypes.Add(parameterType);
            //    }
            //}
            Frame frame = Windows.UI.Xaml.Window.Current.Content as Frame;
            frame.Navigate(typeof(T), parameter);
        }

        public void NavigateBack()
        {
            Frame frame = Windows.UI.Xaml.Window.Current.Content as Frame;
            if (frame != null && frame.CanGoBack)
            {
                frame.GoBack();
            }
        }

        public void NavigateForward()
        {
            Frame frame = Windows.UI.Xaml.Window.Current.Content as Frame;
            if (frame != null && frame.CanGoForward)
            {
                frame.GoForward();
            }
        }

        public void NavigateBackOrTo<T>() where T : Page
        {
            Frame frame = Windows.UI.Xaml.Window.Current.Content as Frame;
            if (frame.CanGoBack)
            {
                frame.GoBack();
            }
            else
            {
                frame.Navigate(typeof(T));
            }
        }

        public static void ClearNavigationBackStack()
        {
            Frame frame = Windows.UI.Xaml.Window.Current.Content as Frame;
            if (frame != null)
            {
                frame.BackStack.Clear();
            }
        }


        public string GetNavigationParameter(string paramName, NavigationEventArgs e)
        {
            string query = e.Parameter as string;
            if (query == null)
            {
                return null;
            }
            int startIndex;
            int length = paramName.Length + 1;
            if (query.StartsWith(paramName + "="))
            {
                startIndex = 0;
            }
            else
            {
                length++;
                startIndex = query.IndexOf("?" + paramName + "=");
                if (startIndex == -1)
                {
                    startIndex = query.IndexOf("&" + paramName + "=");
                    if (startIndex == -1)
                    {
                        return null;
                    }
                }
            }
            query = query.Substring(startIndex + length);
            int stopIndex = query.IndexOf('&');
            if (stopIndex != -1)
            {
                query = query.Substring(0, stopIndex);
            }
            return Uri.UnescapeDataString(query);
        }

        /// <summary>
        /// Runs the provided action or method on the UI thread, example usage:
        /// await RunOnUiThreadAsync( () => { do my UI stuff here... } );
        /// </summary>
        /// <param name="handler">the code that needs to run on the UI thread</param>
        /// <returns>this call is awaitable</returns>
        public async Task RunOnUiThreadAsync(DispatchedHandler handler)
        {
            await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
                Windows.UI.Core.CoreDispatcherPriority.Normal, handler);
        }


        public string EscapeNavigationParameter(string paramValue)
        {
            return Uri.EscapeDataString(paramValue);
        }

        public virtual void OnNavigatedTo(NavigationEventArgs e)
        {
        }


        public virtual void OnNavigatedFrom(NavigationEventArgs e)
        {
        }


        /// <summary>
        /// Allows subclasses to handle the hardware back button on Windows Phone devices
        /// </summary>
        /// <returns>true when the event was handled by the subclass</returns>
        public virtual bool OnHardwareButtonsBackPressed()
        {
            return false;
        }

        /// <summary>
        /// Shows a message dialog
        /// </summary>
        /// <param name="message">the message</param>
        /// <param name="title">the optional title</param>
        /// <param name="labelOkText">IF a 'report' button should be shown, the label for the dismiss/ok action</param>
        /// <param name="labelReportText">IF a 'report' button should be shown, the label for the 'report via email' action</param>
        /// <param name="reportSubject">The optional subject for the report email</param>
        /// <param name="reportToAddress">The optional to address for the report email like support@company.com</param>
        /// 
        /// <returns>this call is awaitable</returns>
        public Task ShowMessageDialogAsync(string message, string title = null, string labelOkText = null, string labelReportText = null, string reportToAddress = null, string reportSubject = null, string reportBody = null)
        {
            return MessageDialogHelper.ShowMessageDialogAsync(message, title, labelOkText, labelReportText, reportToAddress, reportSubject, reportBody);
        }


        /// <summary>
        /// Is called when the app is being activated, e.g. on WP when taking a snapshot.
        /// Note that you need to either extend Enough.BaseApp or call this method from within App.OnActivated().
        /// </summary>
        /// <param name="e">the activation event</param>
        public virtual void OnAppActivated(IActivatedEventArgs e)
        {
            // let subclasses implement this if they need it
        }

        /// <summary>
        /// Gets the current app version and information whether it has been updated.
        /// </summary>
        /// <returns>the current app version</returns>
        public AppVersion GetAppVersionAndUpdateInformation()
        {
            string versionStr = AppVersion.GetVersion(Package.Current.Id.Version);
            string previousVersionStr = Windows.Storage.ApplicationData.Current.LocalSettings.Values["AppVersion"] as string;
            if (previousVersionStr != versionStr)
            {
                // Our app has been updated
                Windows.Storage.ApplicationData.Current.LocalSettings.Values["AppVersion"] = versionStr;
            }
            return new AppVersion { CurrentVersion = versionStr, PreviousVersion = previousVersionStr };
        }

    }
}
